﻿#include "libcomm.h"
#if defined(__GNUC__) && !defined(__CYGWIN__)
#include <sys/syscall.h>
#endif

#ifdef __GNUC__
static void __wait_cleanup(void *arg)
{    
	LOGWARN("thread(%d) was canceled while Thread::Sleep().",Thread::Id());
}
#endif

void Thread::Sleep(unsigned int timeoutMs)
{
#ifdef COMM_MSVC
	::Sleep(timeoutMs);
#else
	pthread_cleanup_push(__wait_cleanup, NULL);
	MYLIB_ASSERT_ERRNO(0 == usleep(1000*timeoutMs));
	pthread_cleanup_pop(0);
#endif
}

int Thread::Id()
{
#ifdef COMM_MSVC
	DWORD dwId = ::GetCurrentThreadId();
	MYLIB_ASSERT(dwId <= 0x7FFFFFFF);
	return dwId;
#elif defined(__CYGWIN__)
	#warning "!!!was a pseudo thread id in cygwin version!!!"
	unsigned long long pid = (unsigned long long)pthread_self();
	return (Bitset::High(pid) ^ Bitset::Low(pid));
#else
	return syscall(SYS_gettid);
#endif
}

class DefaultThread : public Thread
{
public:
	Thread::ThreadProc m_proc;
	CommType m_arg;
	DefaultThread(Thread::ThreadProc proc, const CommType& arg, const char* threadName)
		:Thread(threadName),m_proc(proc),m_arg(arg){}
	virtual int Process()
	{
		return m_proc(m_arg);
	}
};

Thread* Thread::Run(Thread::ThreadProc proc, const CommType& arg, const char* threadName, bool suspend)
{
	MYLIB_ASSERT_NOTNULL(proc);
	DefaultThread* trd = new DefaultThread(proc,arg,threadName);
	MYLIB_ASSERT_NOTNULL(trd);
	if(!suspend)
		trd->Run();
	return static_cast<Thread*>(trd);
}

////////////////////////////////////////////////////////////////////////// 


Thread::Thread(const char* threadName, int stackSize)
	: m_name(threadName),m_size(stackSize),m_state(TS_INIT),m_exitcode(EXIT_ERROR),m_thread(0)
{
	if(m_name.empty())
	{
		static int index = 0;
		m_name = String::format("trd_%03d",++index);
	}
}

Thread::~Thread(void)
{
}

Thread::handle Thread::Handle() const
{
	return m_thread;
}

const char* Thread::Name() const
{
	return (const char*)m_name;
}

int Thread::__ThreadProcComm(void* arg)
{
	Thread* self = (Thread*)arg;
	int tid = Thread::Id();
	self->m_tid = tid;

	LOGNOTICE("thread(%d) %s is running...", tid, self->Name());
	self->m_exitcode = self->Process();
	LOGNOTICE("thread(%d) %s exit, exit code: %d.", tid, self->Name(), self->m_exitcode);
	MYLIB_ASSERT(self->m_exitcode<EXIT_MIN || self->m_exitcode>EXIT_MAX);
	self->m_state = TS_EXIT;
	return self->m_exitcode;
}

#ifdef COMM_MSVC
DWORD WINAPI Thread::__ThreadProc(void* arg)
{
	return __ThreadProcComm(arg);
}
#else
void* Thread::__ThreadProc(void* arg)
{
#if !defined(__ANDROID__) && !defined(__OHOS__)
	MYLIB_ASSERT_VALUE(0,pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL));
	MYLIB_ASSERT_VALUE(0,pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL));
#endif
	return (reinterpret_cast<void*>(__ThreadProcComm(arg)));
}
#endif

void Thread::Run()
{
	ThreadLockGuard guard(&m_lock);
	if (m_state != TS_INIT)
	{
		LOGWARN("thread %s is not joined yet !", this->Name());
		return;
	}

	m_tid = 0;
	m_exitcode = EXIT_ERROR;
	{
#ifdef COMM_MSVC
		MYLIB_ASSERT_ERRNO(NULL != (m_thread = CreateThread(NULL, (m_size > 0) ? m_size : 0, __ThreadProc, (LPVOID)this, 0, 0)));
#else
		pthread_attr_t attr;
		MYLIB_ASSERT_VALUE(0, pthread_attr_init(&attr));
		MYLIB_ASSERT_VALUE(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE));
		if (m_size > 0)
		{
			MYLIB_ASSERT_VALUE(0, pthread_attr_setstacksize(&attr, m_size));
		}
		MYLIB_ASSERT_VALUE(0, pthread_create(&m_thread, &attr, __ThreadProc, this));
		MYLIB_ASSERT_VALUE(0, pthread_attr_destroy(&attr));
#endif
	}
	m_state = TS_RUNNING;
}

void Thread::Terminate()
{
	ThreadLockGuard guard(&m_lock);
	if(m_state != TS_RUNNING)
		return;

	MYLIB_ASSERT_NOTNULL(m_thread);
#ifdef COMM_MSVC
	MYLIB_ASSERT_ERRNO(TerminateThread(m_thread,EXIT_CANCELED));
#elif defined(__ANDROID__) || defined(__OHOS__)
	LOGERR("%s : not implement.",__FUNCTION__);
#else
	errno = pthread_cancel(m_thread);
	MYLIB_ASSERT_ERRNO(0 == errno || ESRCH == errno);
#endif
	m_exitcode = EXIT_CANCELED;
	LOGNOTICE("thread %s was terminated by force.", this->Name());
	m_state = TS_EXIT;
}

int Thread::Join(unsigned int timeoutMs)
{
	ThreadLockGuard guard(&m_lock);
	if (m_state == TS_INIT)
		return m_exitcode;

	if (Thread::Id() == m_tid)
	{
		LOGWARN("join in running thread will cause a deadlock !");
		return EXIT_ERROR;
	}

	// TS_RUNNING OR TS_EXIT.
	MYLIB_ASSERT_NOTNULL(m_thread);
#ifdef COMM_MSVC

	DWORD dwEvent = WaitForSingleObject(m_thread, timeoutMs);
	switch (dwEvent)
	{
	case WAIT_OBJECT_0:
		break;
	case WAIT_TIMEOUT:
	{
		m_exitcode = EXIT_TIMEOUT;
		LOGWARN("thread %s join timeout after %d ms.", this->Name(), timeoutMs);
	}
	break;
	default:
	{
		m_exitcode = EXIT_ERROR;
		LOGFUNCERROR(WaitForSingleObject);
	}
	break;
	}

#else

	int ret = 0;
	if (-1 == timeoutMs)
	{
		ret = pthread_join(m_thread, NULL);
	}
	else
	{
#if defined(__ANDROID__) || defined(__OHOS__)
		LOGERR("%s : timout not supported.", __FUNCTION__);
		ret = pthread_join(m_thread, NULL);
#else
		timespec ts = Timer::MakeTimespec(timeoutMs);
		ret = pthread_timedjoin_np(m_thread, NULL, &ts);
#endif
	}

	if (0 != ret && ESRCH != ret) // ESRCH: thread have exit before.
	{
		if (ETIMEDOUT == ret)
		{
			m_exitcode = EXIT_TIMEOUT;
			LOGWARN("thread %s join timeout after %d ms.", this->Name(), timeoutMs);
		}
		else
		{
			m_exitcode = EXIT_ERROR;
			LOGFUNCERRORNO(pthread_timedjoin_np, ret);
		}
	}

#endif
	
	
	// clear resource.
	//
	if (EXIT_TIMEOUT != m_exitcode)
	{
		if (0 != m_thread)
		{
#ifdef COMM_MSVC
			MYLIB_ASSERT_ERRNO(CloseHandle(m_thread));
#endif
		}
		m_tid = 0;
		m_thread = 0;
		m_state = TS_INIT;
		//LOGNOTICE("thread %s is joined !", this->Name());
	}
	
	return m_exitcode;
}

